#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QtGui>
#include <QtXml>
#include <QNetworkReply>

#include "pdftool.h"
#include "qswiftprogressdialog.h"
#include "browser.h"
#include "ratingwidget.h"
#include "naturaltreewidgetitem.h"
#include "downloadwidget.h"

class NoPaintItemDelegate : public QItemDelegate
{
public:
	explicit NoPaintItemDelegate()
	{}

	void paint(QPainter* , const QStyleOptionViewItem& , const QModelIndex& ) const
	{
	}
};

MainWindow::MainWindow(QWidget *parent) :
	QMainWindow(parent),
	ui(new Ui::MainWindow)
{
	ui->setupUi(this);

#ifdef WIN32
	QFont font;
	font.setPointSize(9);
	setFont(font);
	ui->tabPDFs->setFont(font);

	QFont font1;
	font1.setPointSize(10);
	ui->treeLibrary->setFont(font1);
#endif

	firstShowHandled = false;

	QDesktopWidget desktop;
	setMaximumSize(desktop.size());

	ignoreSortOrderChanges = false;

	QButtonGroup *leftButtonGroup = new QButtonGroup(this);
	leftButtonGroup->addButton(ui->btnLibrary);
	leftButtonGroup->addButton(ui->btnReader);
	leftButtonGroup->addButton(ui->btnBrowser);
	leftButtonGroup->addButton(ui->btnDownloads);
	leftButtonGroup->addButton(ui->btnOptions);

	ui->btnReader->setEnabled(false);

	ui->pageBrowser->setLayout(new QVBoxLayout());
	ui->pageBrowser->layout()->setContentsMargins(0, 0, 0, 0);
	browser = new Browser(ui->pageBrowser);
	ui->pageBrowser->layout()->addWidget(browser);
	connect(browser, SIGNAL(metadataDownloaded(QString)), this, SLOT(onMetadataDownloaded(QString)));
	connect(browser, SIGNAL(pdfDownloaded(QByteArray,QString)), this, SLOT(onPdfDownloaded(QByteArray,QString)));
	connect(browser, SIGNAL(newDownloadStarting(QNetworkReply*,QString,QString)), this, SLOT(onNewDownloadStarting(QNetworkReply*,QString,QString)), Qt::DirectConnection);

	ui->treeLibrary->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->treeLibrary, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint)));

	libraryContextMenu = new QMenu(ui->treeLibrary);
	libraryContextMenu->setStyleSheet("QMenu { margin: 3px; } ");
	ui->treeLibrary->setContextMenuPolicy(Qt::CustomContextMenu);
	QAction *actionLookup = libraryContextMenu->addAction("Lookup on the web");
	connect(actionLookup, SIGNAL(triggered()), this, SLOT(onLookupRequested()));
	QAction *actionDelete = libraryContextMenu->addAction("Delete from library");
	connect(actionDelete, SIGNAL(triggered()), this, SLOT(onDeleteRequested()));

	ui->tabPDFs->setTabsClosable(true);

	setupTreeColumns();

	library = new Library(QDir::homePath() + "/Documents/Fundamental",
						  loadAutoGit(),
						  this);

	if (loadAutoGitDelayed()) {
		saveAutoGitDelayed(false);
		QString message;
		if (!library->setAutoGit(true, message)) {
			QMessageBox::critical(this,
								  "Glitch while enabling git sync",
								  message,
								  QMessageBox::Ok);
		} else {
			QMessageBox::information(this,
									 "Git sync",
									 "Git support successfully enabled!",
									 QMessageBox::Ok);
		}
		saveAutoGit();
	}

	QString message;
	bool fatal;
	bool errors;
	bool ok = library->init(message, fatal, errors);
	if (!ok || errors || fatal) {
		QMessageBox::critical(this,
							  fatal ? "Error while loading library" : "Glitch while loading library",
							  message,
							  QMessageBox::Ok);
		if (fatal) {
			exit(1);
		}
	}

	ui->btnLibrary->setChecked(true);
	ui->checkOptionsAutoGit->setChecked(library->getAutoGit());

	if (!library->load()) {
		QMessageBox::critical(this, "Error", QString("Could not load library - check access rights.\n Path: %1").arg(library->getPath()));
		exit(1);
	}

	populateLibraryWidget();
	restoreTreeViewColumnSizes();

	connect(ui->treeLibrary->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(onTreeViewSortIndicatorChanged(int,Qt::SortOrder)));
	connect(ui->treeLibrary->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(onTreeViewColumnResized(int,int,int)));

	timerAutoSave = new QTimer(this);
	connect(timerAutoSave, SIGNAL(timeout()), this, SLOT(autoSave()));
	timerAutoSave->start(30 * 60 * 1000);
}

MainWindow::~MainWindow()
{
	delete library;
	delete ui;
}

void MainWindow::setupTreeColumns()
{
	QTreeWidgetItem *header = new NaturalTreeWidgetItem((QTreeWidget*)0,
														QStringList() << "Year" << "Citations" << "Title" << "Author" << "Page count" << "Published in" << "Rating" << "Read" << "Tags");
	ui->treeLibrary->setHeaderItem(header);
	for (int iCol = 0; iCol < ui->treeLibrary->headerItem()->columnCount(); iCol++) {
		if (ui->treeLibrary->headerItem()->data(iCol, Qt::DisplayRole).toString() == "Rating") {
			ui->treeLibrary->setItemDelegateForColumn(iCol, new NoPaintItemDelegate);
		}
	}
	restoreTreeViewSortOrder();
}

void MainWindow::populateLibraryWidgetItem(QTreeWidgetItem *item, LibraryDoc doc)
{
	QStringList columns;
	for (int iCol = 0; iCol < ui->treeLibrary->headerItem()->columnCount(); iCol++) {
		columns << ui->treeLibrary->headerItem()->data(iCol, Qt::DisplayRole).toString();
	}

	for (int iCol = 0; iCol < columns.count(); iCol++) {
		item->setData(iCol, Qt::DisplayRole, doc.getValue(columns[iCol]));
		item->setData(iCol, Qt::ToolTipRole, item->data(iCol, Qt::DisplayRole).toString());
	}
	item->setData(0, Qt::UserRole, doc.getHash());
	item->setFlags(item->flags() | Qt::ItemIsEditable);
}

void MainWindow::setupRatingColum()
{
	for (int iCol = 0; iCol < ui->treeLibrary->headerItem()->columnCount(); iCol++) {
		if (ui->treeLibrary->headerItem()->data(iCol, Qt::DisplayRole).toString() == "Rating") {
			for (QTreeWidgetItemIterator it(ui->treeLibrary); *it; ++it) {
				QTreeWidgetItem *item = *it;
				QByteArray hash = item->data(0, Qt::UserRole).toByteArray();
				if (hash.isEmpty())
					continue;
				RatingWidget *ratingWidget = new RatingWidget(hash, item->data(iCol, Qt::DisplayRole).toString().toInt());
				connect(ratingWidget, SIGNAL(ratingChanged(QByteArray,int)), this, SLOT(onRatingChanged(QByteArray,int)));
				ui->treeLibrary->setItemWidget(item, iCol, ratingWidget);
				item->setData(iCol, Qt::SizeHintRole, ratingWidget->sizeHint());
			}
		}
	}
}

void MainWindow::populateLibraryWidget()
{
	setUpdatesEnabled(false);
	ui->treeLibrary->setSortingEnabled(false);

	ui->treeLibrary->clear();
	QList<QTreeWidgetItem *> items;
	foreach (LibraryDoc doc, library->getDocs().values()) {
		QTreeWidgetItem *item = new NaturalTreeWidgetItem();
		populateLibraryWidgetItem(item, doc);
		items << item;
	}
	ui->treeLibrary->addTopLevelItems(items);
	setupRatingColum();
	ui->treeLibrary->setSortingEnabled(true);
	setUpdatesEnabled(true);

	if (!ui->txtSearch->text().isEmpty()) {
		populateLibraryWidgetSearch(library->search(ui->txtSearch->text()));
	}
}

void MainWindow::populateLibraryWidgetSearch(QList<LibrarySearchResult> results)
{
	setUpdatesEnabled(false);
	ui->treeLibrary->setSortingEnabled(false);

	ui->treeLibrary->clear();

	QList<QTreeWidgetItem *> items;
	foreach (LibrarySearchResult r, results) {
		LibraryDoc doc = r.doc;
		QTreeWidgetItem *item = new NaturalTreeWidgetItem();
		populateLibraryWidgetItem(item, doc);
		items << item;
	}
	ui->treeLibrary->addTopLevelItems(items);
	setupRatingColum();
	ui->treeLibrary->sortByColumn(-1);
	ui->treeLibrary->setSortingEnabled(true);
	setUpdatesEnabled(true);
}

QByteArray MainWindow::importFile(QString fileName, QString tags)
{
	if (fileName.isEmpty())
		return QByteArray();

	QFileInfo fileInfo(fileName);
	QString fileNameNoPath = fileInfo.fileName();

	QFile file(fileName);
	if (!file.open(QIODevice::ReadOnly)) {
		QMessageBox::critical(this, "Error", QString("Could not read file: %1").arg(file.fileName()));
		return QByteArray();
	}
	QByteArray fileHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Sha1);

	QString destName = library->getPath() + "/" + fileHash.toHex() + ".pdf";
	if (!QFile::copy(fileName, destName) && !QFile(destName).exists()) {
		QMessageBox::critical(this, "Error", QString("Could not copy file from: %1 to: %2").arg(fileName).arg(destName));
		return QByteArray();
	}

	LibraryDoc doc(fileHash);
	doc.setValue("File name", fileNameNoPath);
	PDFTool pdfTool(destName);
	doc.setValue("Title", pdfTool.guessTitle());
	doc.setValue("Page count", QString::number(pdfTool.pageCount()));
	doc.setValue("Tags", tags);
	doc.setFullText(pdfTool.fullText());

	if (!library->addFile(doc)) {
		if (!library->getDocs().contains(fileHash)) {
			QDir dir(library->getPath());
			dir.remove(destName);
		} else {
			QString currentTags = library->getFileData(fileHash, "Tags").trimmed();
			QString newTags = currentTags + QString(currentTags.isEmpty() ? "" : ", ") + tags;
			newTags = QStringList(newTags.split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts).toSet().toList()).join(", ");
			library->setFileData(fileHash, "Tags", newTags);
		}
		return QByteArray();
	}

	return fileHash;
}

void MainWindow::importFileFromViewer(QString fileName)
{
	QByteArray hash = importFile(fileName);
	if (hash.isEmpty())
		return;

	// update
	populateLibraryWidget();

	// make pdf in viewer not importable
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v && v->getFileName() == fileName) {
			v->setImportable(false);
			ui->tabPDFs->tabBar()->setTabTextColor(i, Qt::black);
		}
	}

	// select item
	for (QTreeWidgetItemIterator it(ui->treeLibrary); *it; ++it) {
		QTreeWidgetItem *item = *it;
		if (item->data(0, Qt::UserRole).toByteArray() == hash) {
			ui->treeLibrary->setCurrentItem(item, 0);
			break;
		}
	}
	ui->btnLibrary->setChecked(true);
}

void MainWindow::openFile(QByteArray dochHash)
{
	LibraryDoc doc = library->getDocs().value(dochHash);
	if (doc.isNull())
		return;

	QString displayName;
	QString title = doc.getValue("Title");
	if (!title.isEmpty()) {
		displayName = title;
	} else {
		QString fileName = doc.getValue("File name");
		if (!fileName.isEmpty()) {
			displayName = fileName;
		} else {
			displayName = dochHash;
		}
	}
	openFile(library->getPath() + "/" + dochHash.toHex() + ".pdf", displayName, dochHash);
}

void MainWindow::openFile(QString fileName, QString displayName, QByteArray dochHash, bool unimported)
{
	// see if it is not open already
	PDFViewer *viewer = NULL;
	int index = -1;
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v && v->getFileName() == fileName) {
			viewer = v;
			index = i;
			break;
		}
	}
	library->setOpen(dochHash);
	library->loadPageCache(dochHash);

	if (!viewer) {
		viewer = new PDFViewer(ui->tabPDFs);
		index = ui->tabPDFs->addTab(viewer, QIcon(":/icons/application-pdf.png"), displayName);
		ui->tabPDFs->setTabToolTip(index, displayName);
		connect(viewer, SIGNAL(fullScreenToggled(bool)), this, SLOT(setFullScreen(bool)));
		connect(viewer, SIGNAL(controlsVisibleToggled(bool)), this, SLOT(onControlsVisibleToggled(bool)));
		connect(viewer, SIGNAL(showControls(bool)), this, SLOT(onShowControls(bool)));
		connect(viewer, SIGNAL(importClicked(QString)), this, SLOT(importFileFromViewer(QString)));
		connect(viewer, SIGNAL(textNoteCreationRequested(PDFViewer*,QByteArray,int,QPointF,QColor)), this, SLOT(onTextNoteCreationRequested(PDFViewer*,QByteArray,int,QPointF,QColor)));
		connect(viewer, SIGNAL(textNoteDeleteRequested(PDFViewer*,QByteArray,int)), this, SLOT(onTextNoteDeleteRequested(PDFViewer*,QByteArray,int)));
		connect(viewer, SIGNAL(textNoteTitleChanged(PDFViewer*,QByteArray,int,QString)), this, SLOT(onTextNoteTitleChanged(PDFViewer*,QByteArray,int,QString)));
		connect(viewer, SIGNAL(textNoteTextChanged(PDFViewer*,QByteArray,int,QString)), this, SLOT(onTextNoteTextChanged(PDFViewer*,QByteArray,int,QString)));
		connect(viewer, SIGNAL(textNotePosChanged(PDFViewer*,QByteArray,int,QPointF)), this, SLOT(onTextNotePosChanged(PDFViewer*,QByteArray,int,QPointF)));
		connect(viewer, SIGNAL(textNoteColorChanged(PDFViewer*,QByteArray,int,QColor)), this, SLOT(onTextNoteColorChanged(PDFViewer*,QByteArray,int,QColor)));
		connect(viewer, SIGNAL(textNoteMaximizedChanged(PDFViewer*,QByteArray,int,bool)), this, SLOT(onTextNoteMaximizedChanged(PDFViewer*,QByteArray,int,bool)));
		connect(viewer, SIGNAL(textNoteMaxSizeChanged(PDFViewer*,QByteArray,int,QSize)), this, SLOT(onTextNoteMaxSizeChanged(PDFViewer*,QByteArray,int,QSize)));
		connect(viewer, SIGNAL(reportTextChanged(PDFViewer*,QByteArray,QString)), this, SLOT(onReportTextChanged(PDFViewer*,QByteArray,QString)));
		connect(viewer, SIGNAL(highlightCreationRequested(PDFViewer*,QByteArray,int,QRectF,QColor)), this, SLOT(onHighlightCreationRequested(PDFViewer*,QByteArray,int,QRectF,QColor)));
		connect(viewer, SIGNAL(highlightDeletionRequested(PDFViewer*,QByteArray,int,QRectF)), this, SLOT(onHighlightDeletionRequested(PDFViewer*,QByteArray,int,QRectF)));
		connect(viewer, SIGNAL(interDocumentAnchorRequested(PDFViewer*,QByteArray,int,QPointF)), this, SLOT(onInterDocumentAnchorRequested(PDFViewer*,QByteArray,int,QPointF)));
		connect(viewer, SIGNAL(navigationRequested(QByteArray,int,QPointF)), this, SLOT(onNavigationRequested(QByteArray,int,QPointF)));
		connect(viewer, SIGNAL(viewOffsetCHanged(QByteArray,qreal)), this, SLOT(onViewOffsetCHanged(QByteArray,qreal)));
		connect(viewer, SIGNAL(docInfoRequested(PDFViewer*,QByteArray)), this, SLOT(onDocInfoRequested(PDFViewer*,QByteArray)));
		connect(viewer, SIGNAL(pageCacheChanged(PDFViewer*,QByteArray)), this, SLOT(onDocPageCacheChanged(PDFViewer*,QByteArray)));
		viewer->setControlsVisible(loadShowControls());
		viewer->setFullScreen(this->isFullScreen());
		viewer->loadDocument(fileName, dochHash, library->getPageCache(dochHash));
		QList<TextNoteAnnotation> annotations = library->getTextNoteAnnotations(dochHash);
		for (int iAnn = 0; iAnn < annotations.count(); iAnn++) {
			viewer->createTextNote(annotations[iAnn]);
		}
		QHash<int, QList<HighlightAnnotation> > highlights = library->getHighlights(dochHash);
		foreach (int pageNumber, highlights.uniqueKeys()) {
			foreach (HighlightAnnotation h, highlights[pageNumber]) {
				viewer->createTextHighlight(h);
			}
		}
		viewer->setReportText(library->getReportText(dochHash));
		viewer->setViewOffset(library->getViewOffset(dochHash));
		// suggested save name
		{
			QString title = library->getFileData(dochHash, "Title");
			QString author = library->getFileData(dochHash, "Author");
			QString year = library->getFileData(dochHash, "Year");
			QString suggestedName;
			if (!year.isEmpty()) {
				suggestedName = year;
				if (!title.isEmpty()) {
					suggestedName += " - " + title;
					if (!author.isEmpty()) {
						suggestedName += " - " + author;
					} else {}
				} else {
					if (!author.isEmpty()) {
						suggestedName += " - " + author;
					} else {}
				}
			} else {
				if (!title.isEmpty()) {
					suggestedName += title;
					if (!author.isEmpty()) {
						suggestedName += " - " + author;
					} else {}
				} else {
					if (!author.isEmpty()) {
						suggestedName += author;
					} else {}
				}
			}
			if (!suggestedName.isEmpty()) {
				suggestedName += ".pdf";
				suggestedName = suggestedName.simplified();
				suggestedName = suggestedName.replace(QRegExp("[^a-zA-Z0-9\\-\\.,\\s\\(\\)]"), QString("_"));
				viewer->setSuggestedFileName(suggestedName);
			}
		}
	}

	viewer->setImportable(unimported);

	if (!unimported) {
		ui->tabPDFs->tabBar()->setTabTextColor(index, Qt::black);
	} else {
		ui->tabPDFs->tabBar()->setTabTextColor(index, Qt::blue);
	}

	ui->btnReader->setEnabled(true);
	ui->btnReader->setChecked(true);
	ui->tabPDFs->setCurrentIndex(index);
}

void MainWindow::openExternalFile(QString fileName)
{
	QString displayName;
	QFileInfo fileInfo(fileName);
	displayName = fileInfo.completeBaseName();
	if (displayName.isEmpty())
		displayName = fileName;

	openFile(fileName, displayName, QByteArray(), true);
}

void MainWindow::showEvent(QShowEvent *event)
{
	QMainWindow::showEvent(event);
	if (firstShowHandled)
		return;
	firstShowHandled = true;
	restoreTreeViewSortOrder();
	QTimer::singleShot(100, this, SLOT(openDocsLastSession()));
}

void MainWindow::closeEvent(QCloseEvent *event)
{
	while (ui->tabPDFs->count() > 0) {
		delete ui->tabPDFs->widget(0);
	}
	QString message;
	if (!library->destroy(message)) {
		QMessageBox::critical(this,
							  "Error while closing library",
							  message,
							  QMessageBox::Ok);
	}
	QMainWindow::closeEvent(event);
}

void MainWindow::openDocsLastSession()
{
	foreach (QByteArray docHash, library->getOpenDocs()) {
		openFile(docHash);
	}
}

void MainWindow::autoSave()
{
	library->save();
}

QString fileNameFromPath(QString path)
{
	QFileInfo pathInfo(path);
	return pathInfo.fileName();
}

QString absolutePathFromPath(QString path)
{
	QFileInfo pathInfo(path);
	return pathInfo.canonicalFilePath();
}

void MainWindow::on_actionScan_directory_triggered()
{

}

void MainWindow::onControlsVisibleToggled(bool value)
{
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v) {
			v->setControlsVisible(value);
		}
	}
	saveShowControls(value);
}

void MainWindow::onShowControls(bool value)
{
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v) {
			v->onShowControls(value);
		}
	}
	ui->leftBar->setVisible(value);
	QWidget *tabBar = ui->tabPDFs->findChild<QTabBar *>(QLatin1String("qt_tabwidget_tabbar"));
	if (tabBar)
		tabBar->setVisible(value);
}

void MainWindow::setFullScreen(bool value)
{
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v) {
			v->setFullScreen(value);
		}
	}
	if (value) {
		showFullScreen();
	} else {
		show();
		showNormal();
		showMaximized();
	}
}

void MainWindow::on_btnLibrary_toggled(bool checked)
{
	if (checked) {
		ui->stackedWidget->setCurrentWidget(ui->pageLibrary);
		ui->treeLibrary->setFocus();
	}
}

void MainWindow::on_btnReader_toggled(bool checked)
{
	if (checked) {
		ui->stackedWidget->setCurrentWidget(ui->pageReader);
		ui->tabPDFs->setFocus();
	}
}

void MainWindow::on_btnOptions_toggled(bool checked)
{
	if (checked) {
		ui->stackedWidget->setCurrentWidget(ui->pageOptions);
	}
}

void MainWindow::on_btnBrowser_toggled(bool checked)
{
	if (checked) {
		ui->stackedWidget->setCurrentWidget(ui->pageBrowser);
		browser->setFocus();
	}
}

void MainWindow::on_btnDownloads_toggled(bool checked)
{
	if (checked) {
		ui->stackedWidget->setCurrentWidget(ui->pageDownloads);
	}
}

void MainWindow::onNewDownloadStarting(QNetworkReply *reply, QString url, QString mime)
{
	QVBoxLayout *layout = dynamic_cast<QVBoxLayout *>(ui->scrollDownloadsContents->layout());
	if (layout) {
		DownloadWidget *dw = new DownloadWidget(ui->scrollDownloadsContents);
		layout->insertWidget(0, dw);

		dw->setUrl(url);
		dw->setMime(mime);
		dw->setSize(-1);
		dw->setProgress(0);
		connect(reply, SIGNAL(downloadProgress(qint64,qint64)), dw, SLOT(setProgress(qint64,qint64)));
	}
}

void MainWindow::on_btnImportFile_clicked()
{
	QString fileName = QFileDialog::getOpenFileName(this, tr("Import file"), QString(), tr("PDF Documents (*.pdf)"));

	QString tags = QInputDialog::getText(this, "Add tags", "Tags:", QLineEdit::Normal, "");

	importFile(fileName, tags);
	populateLibraryWidget();
}

void MainWindow::on_btnImportDir_clicked()
{
	QString rootPath = QFileDialog::getExistingDirectory(this, tr("Import all PDFs from directory"), QString(), QFileDialog::ShowDirsOnly);
	if (rootPath.isEmpty())
		return;

	QString tags = QInputDialog::getText(this, "Add tags", "Tags:", QLineEdit::Normal, "");

	QSwiftProgressDialog progressDialog("Importing documents...", "Listing directory...", 0, 1, this);

	rootPath = absolutePathFromPath(rootPath);

	QList<QString> dirQueue;
	dirQueue << rootPath;

	int totalCount = 0;
	int processedCount = 0;
	while (!dirQueue.isEmpty()) {
		QString dirPath = dirQueue.takeFirst();
		QString relativeDirPath = dirPath;
		if (relativeDirPath.startsWith(rootPath)) {
			relativeDirPath = relativeDirPath.mid(rootPath.length());
		}

		// open dir
		QDir dir;
		if (!dir.cd(dirPath)) {
			qDebug() << __FILE__ << __LINE__ << "Failed to open dir:" << dirPath;
			exit(-1);
		}

		// list subdirs
		dir.setFilter(QDir::Dirs | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot);
		dir.setSorting(QDir::Size | QDir::Reversed);
		QFileInfoList list = dir.entryInfoList();
		for (int i = 0; i < list.size(); i++) {
			QFileInfo fileInfo = list.at(i);
			dirQueue << fileInfo.canonicalFilePath();
		}

		// list files
		dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot);
		dir.setSorting(QDir::Size | QDir::Reversed);
		dir.setNameFilters(QStringList() << "*.pdf");
		list = dir.entryInfoList();

		totalCount += list.size();
		progressDialog.setRange(0, totalCount);
		for (int i = 0; i < list.size(); i++) {
			QFileInfo fileInfo = list.at(i);
			QString filePath = fileInfo.canonicalFilePath();

			progressDialog.setValue(processedCount);
			progressDialog.setText("Processing " + filePath);
			processedCount++;

			// process file
			importFile(filePath, tags);
		}
	}

	populateLibraryWidget();
}

void MainWindow::on_btnOpenExternalFile_clicked()
{
	QString fileName = QFileDialog::getOpenFileName(this, tr("Open external file"), QString(), tr("PDF Documents (*.pdf)"));
	if (fileName.isEmpty())
		return;

	openExternalFile(fileName);
}

void MainWindow::onTextNoteCreationRequested(PDFViewer *v, QByteArray docHash, int pageNumber, QPointF pos, QColor color)
{
	TextNoteAnnotation note = library->addTextNoteAnnotation(docHash, pageNumber, pos, color);
	if (note.getDocHash().isNull())
		return;
	if (!v)
		return;
	v->createTextNote(note);
}

void MainWindow::onTextNoteDeleteRequested(PDFViewer *v, QByteArray docHash, int id)
{
	library->deleteTextNoteAnnotation(docHash, id);
	if (!v)
		return;
	v->deleteTextNote(id);
}

void MainWindow::onTextNoteTitleChanged(PDFViewer *v, QByteArray docHash, int id, QString title)
{
	Q_UNUSED(v);
	library->changeTextNoteAnnotationTitle(docHash, id, title);
}

void MainWindow::onTextNoteTextChanged(PDFViewer *v, QByteArray docHash, int id, QString text)
{
	Q_UNUSED(v);
	library->changeTextNoteAnnotationText(docHash, id, text);
}

void MainWindow::onTextNotePosChanged(PDFViewer *v, QByteArray docHash, int id, QPointF pos)
{
	Q_UNUSED(v);
	library->changeTextNoteAnnotationPos(docHash, id, pos);
}

void MainWindow::onTextNoteColorChanged(PDFViewer *v, QByteArray docHash, int id, QColor color)
{
	Q_UNUSED(v);
	library->changeTextNoteAnnotationColor(docHash, id, color);
}

void MainWindow::onTextNoteMaximizedChanged(PDFViewer *v, QByteArray docHash, int id, bool value)
{
	Q_UNUSED(v);
	library->changeTextNoteAnnotationMaximized(docHash, id, value);
}

void MainWindow::onTextNoteMaxSizeChanged(PDFViewer *v, QByteArray docHash, int id, QSize maxSize)
{
	Q_UNUSED(v);
	library->changeTextNoteMaximumSize(docHash, id, maxSize);
}

void MainWindow::onReportTextChanged(PDFViewer *v, QByteArray docHash, QString text)
{
	Q_UNUSED(v);
	library->changeReportText(docHash, text);
}

void MainWindow::onHighlightCreationRequested(PDFViewer *v, QByteArray docHash, int pageNumber, QRectF box, QColor color)
{
	HighlightAnnotation h = library->createHighlight(docHash, pageNumber, box, color);
	if (h.getDocHash().isNull())
		return;
	if (!v)
		return;
	v->createTextHighlight(h);
}

void MainWindow::onHighlightDeletionRequested(PDFViewer *v, QByteArray docHash, int pageNumber, QRectF box)
{
	library->deleteHighlight(docHash, pageNumber, box);
	if (!v)
		return;
	v->deleteTextHighlight(pageNumber, box);
}

void MainWindow::onInterDocumentAnchorRequested(PDFViewer *v, QByteArray docHash, int pageNumber, QPointF normalizedPos)
{
	Q_UNUSED(v);
	QString title = library->getFileData(docHash, "Title");
	QString author = library->getFileData(docHash, "Author");
	QString pub = library->getFileData(docHash, "Published in");
	QString year = library->getFileData(docHash, "Year");
	QString reference;
	if (!author.isEmpty()) {
		reference += author;
		if (!title.isEmpty()) {
			reference += ". <i>" + title + "</i>";
			if (!pub.isEmpty()) {
				reference += ", " + pub;
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			} else {
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			}
		} else {
			if (!pub.isEmpty()) {
				reference += ". " + pub;
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			} else {
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			}
		}
	} else {
		if (!title.isEmpty()) {
			reference += "<i>" + title + "</i>";
			if (!pub.isEmpty()) {
				reference += ", " + pub;
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			} else {
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			}
		} else {
			if (!pub.isEmpty()) {
				reference += pub;
				if (!year.isEmpty()) {
					reference += ", " + year;
				} else {
					// done
				}
			} else {
				if (!year.isEmpty()) {
					reference += year;
				} else {
					// done
				}
			}
		}
	}
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v2 = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v2) {
			if (v2->tryCreateInterDocAnchor(docHash, pageNumber, normalizedPos, reference)) {
				break;
			}
		}
	}
}

void MainWindow::onNavigationRequested(QByteArray docHash, int pageNumber, QPointF normPos)
{
	if (docHash.isEmpty())
		return;
	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v2 = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v2) {
			if (v2->getDocHash() == docHash) {
				v2->onNavigationRequested(pageNumber, normPos);
				ui->tabPDFs->setCurrentWidget(v2);
				return;
			}
		}
	}
	openFile(docHash);

	for (int i = 0; i < ui->tabPDFs->count(); i++) {
		PDFViewer *v2 = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(i));
		if (v2) {
			if (v2->getDocHash() == docHash) {
				v2->onNavigationRequested(pageNumber, normPos);
				ui->tabPDFs->setCurrentWidget(v2);
				return;
			}
		}
	}
}

void MainWindow::onTreeViewSortIndicatorChanged(int logicalIndex, Qt::SortOrder order)
{
	if (logicalIndex < 0)
		return;
	if (ignoreSortOrderChanges)
		return;
	QString sortColumn = ui->treeLibrary->headerItem()->data(logicalIndex, Qt::DisplayRole).toString();
	for (int i = 0; i < columnSortOrder.count(); i++) {
		if (columnSortOrder[i].first == sortColumn) {
			columnSortOrder.removeAt(i);
			i--;
		}
	}
	columnSortOrder << QPair<QString, qint32>(sortColumn, order);
	saveTreeViewSortOrder();
}

void MainWindow::restoreTreeViewSortOrder()
{
	ignoreSortOrderChanges = true;

	loadTreeViewSortOrder();
	QStringList columns;
	QHash<QString, int> colName2Index;
	for (int c = 0; c < ui->treeLibrary->headerItem()->columnCount(); c++) {
		columns << ui->treeLibrary->headerItem()->data(c, Qt::DisplayRole).toString();
		colName2Index[columns.last()] = c;
	}
	ui->treeLibrary->setSortingEnabled(true);
	for (int i = 0; i < columnSortOrder.count(); i++) {
		QString colName = columnSortOrder[i].first;
		if (colName2Index.contains(colName)) {
			ui->treeLibrary->header()->setSortIndicator(colName2Index[colName], (Qt::SortOrder)columnSortOrder[i].second);
		}
	}

	ignoreSortOrderChanges = false;
}

void MainWindow::saveTreeViewSortOrder()
{
	QByteArray buffer;
	{
		QDataStream out(&buffer, QIODevice::WriteOnly);
		out << columnSortOrder;
	}
	QSettings settings;
	settings.setValue("UILibrary/columnSortOrder", QVariant::fromValue(buffer));
}

bool MainWindow::loadAutoGit()
{
	QSettings settings;
	return settings.value("UIOptions/autoGit", QVariant::fromValue(false)).toBool();
}

void MainWindow::saveAutoGit()
{
	QSettings settings;
	settings.setValue("UIOptions/autoGit", QVariant::fromValue(library->getAutoGit()));
}

bool MainWindow::loadAutoGitDelayed()
{
	QSettings settings;
	return settings.value("UIOptions/autoGitDelayed", QVariant::fromValue(false)).toBool();
}

void MainWindow::saveAutoGitDelayed(bool value)
{
	QSettings settings;
	settings.setValue("UIOptions/autoGitDelayed", QVariant::fromValue(value));
}

void MainWindow::loadTreeViewSortOrder()
{
	QByteArray buffer;
	QSettings settings;
	buffer = settings.value("UILibrary/columnSortOrder", QVariant::fromValue(QByteArray())).toByteArray();
	if (buffer.isEmpty()) {
		columnSortOrder.clear();
	} else {
		QDataStream in(&buffer, QIODevice::ReadOnly);
		in >> columnSortOrder;
	}
}

void MainWindow::onTreeViewColumnResized(int, int, int)
{
	saveTreeViewColumnSizes();
}

void MainWindow::saveTreeViewColumnSizes()
{
	QHash<QString, int> columnSizes;
	for (int c = 0; c < ui->treeLibrary->headerItem()->columnCount(); c++) {
		QString colName = ui->treeLibrary->headerItem()->data(c, Qt::DisplayRole).toString();
		int w = ui->treeLibrary->header()->sectionSize(c);
		columnSizes[colName] = w;
	}

	QByteArray buffer;
	{
		QDataStream out(&buffer, QIODevice::WriteOnly);
		out << columnSizes;
	}
	QSettings settings;
	settings.setValue("UILibrary/columnSizes", QVariant::fromValue(buffer));
}

void MainWindow::restoreTreeViewColumnSizes()
{
	QHash<QString, int> columnSizes;
	QByteArray buffer;
	QSettings settings;
	buffer = settings.value("UILibrary/columnSizes", QVariant::fromValue(QByteArray())).toByteArray();
	if (!buffer.isEmpty()) {
		QDataStream in(&buffer, QIODevice::ReadOnly);
		in >> columnSizes;
	}

	// fallback
	QList<int> autoColumnWidths;
	{
		QFontMetrics fm(ui->treeLibrary->font());

		for (int iCol = 0; iCol < ui->treeLibrary->headerItem()->columnCount(); iCol++) {
			autoColumnWidths << qMax(1, 20 + fm.width(ui->treeLibrary->headerItem()->data(iCol, Qt::DisplayRole).toString()));
		}

		for (QTreeWidgetItemIterator it(ui->treeLibrary); *it; ++it) {
			QTreeWidgetItem *item = *it;
			for (int iCol = 0; iCol < ui->treeLibrary->headerItem()->columnCount(); iCol++) {
				autoColumnWidths[iCol] = qMax(autoColumnWidths[iCol], fm.width(item->data(iCol, Qt::DisplayRole).toString()));
			}
		}
		for (int i = 0; i < ui->treeLibrary->columnCount(); i++) {
			autoColumnWidths[i] = qMin(400, 10 + autoColumnWidths[i]);
		}
	}

	for (int c = 0; c < ui->treeLibrary->headerItem()->columnCount(); c++) {
		QString colName = ui->treeLibrary->headerItem()->data(c, Qt::DisplayRole).toString();
		if (columnSizes.contains(colName)) {
			ui->treeLibrary->setColumnWidth(c, columnSizes[colName]);
		} else {
			ui->treeLibrary->setColumnWidth(c, autoColumnWidths[c]);
		}
	}
}

void MainWindow::onViewOffsetCHanged(QByteArray docHash, qreal viewOffsetNormalized)
{
	library->setViewOffset(docHash, viewOffsetNormalized);
}

void MainWindow::saveShowControls(bool value)
{
	QSettings settings;
	settings.setValue("UI/showControls", QVariant::fromValue(value));
}

bool MainWindow::loadShowControls()
{
	QSettings settings;
	return settings.value("UI/showControls", QVariant::fromValue(true)).toBool();
}

void MainWindow::onDocInfoRequested(PDFViewer *v, QByteArray docHash)
{
	if (!library->getDocs().contains(docHash))
		return;
	LibraryDoc doc = library->getDocs()[docHash];
	QHash<QString, QString> metadata = doc.getMetadata();
	QList<QString> keys = metadata.uniqueKeys();
	qSort(keys);

	QString result;
	result += QString("Metadata for document %1\n").arg(QString::fromAscii(docHash.toHex()));
	foreach (QString key, keys) {
		QString value = metadata.value(key);
		result += QString("%1: %2\n").arg(key).arg(value);
	}
	v->showDocInfo(result);
}

void MainWindow::onDocPageCacheChanged(PDFViewer *v, QByteArray docHash)
{
	Q_UNUSED(v);
	if (!library->getDocs().contains(docHash))
		return;
	library->setPageCacheDirty(docHash);
}

void MainWindow::on_treeLibrary_itemClicked(QTreeWidgetItem *item, int column)
{
	if (item == ui->treeLibrary->headerItem()) {
		ui->treeLibrary->sortByColumn(column);
	}
}

void MainWindow::on_treeLibrary_itemDoubleClicked(QTreeWidgetItem *item, int )
{
	QByteArray hash = item->data(0, Qt::UserRole).toByteArray();
	if (hash.isEmpty())
		return;
	openFile(hash);
}

void MainWindow::on_tabPDFs_tabCloseRequested(int index)
{
	PDFViewer *v = dynamic_cast<PDFViewer*>(ui->tabPDFs->widget(index));
	QToolButton *gotoButton = ui->btnLibrary;
	if (v && v->isImportable()) {
		gotoButton = ui->btnBrowser;
	}
	if (v) {
		library->setClosed(v->getDocHash());
		library->setPageCache(v->getDocHash(), v->getRenderCache());
	}
	delete ui->tabPDFs->widget(index);
	if (ui->tabPDFs->count() == 0) {
		gotoButton->setChecked(true);
		setFullScreen(false);
		onShowControls(true);
		if (ui->btnReader->isChecked()) {
			gotoButton->setChecked(true);
		}
		ui->btnReader->setEnabled(false);
	}
}

void MainWindow::on_treeLibrary_itemChanged(QTreeWidgetItem *item, int column)
{
	QByteArray hash = item->data(0, Qt::UserRole).toByteArray();
	if (hash.isEmpty())
		return;

	library->setFileData(hash, ui->treeLibrary->headerItem()->data(column, Qt::DisplayRole).toString(),
						 item->data(column, Qt::DisplayRole).toString());
	item->setData(column, Qt::DisplayRole, library->getFileData(hash, ui->treeLibrary->headerItem()->data(column, Qt::DisplayRole).toString()));
	item->setData(column, Qt::ToolTipRole, item->data(column, Qt::DisplayRole).toString());
}

void MainWindow::on_btnSearchClear_clicked()
{
	ui->txtSearch->clear();
}

void MainWindow::on_txtSearch_textChanged(const QString &)
{
	on_txtSearch_editingFinished();
}

void MainWindow::onPdfDownloaded(QByteArray contents, QString url)
{
	Q_UNUSED(url);
	if (contents.isEmpty())
		return;
	// 1. Compute hash and save
	QByteArray hash = QCryptographicHash::hash(contents, QCryptographicHash::Sha1);

	QString fileName = library->getDownloadsPath() + "/" + hash.toHex() + ".pdf";

	QFile file(fileName);
	if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
		QMessageBox::warning(this, "Write error", QString("Failed to open file for writing: %1").arg(file.fileName()));
		return;
	}
	while (!contents.isEmpty()) {
		qint64 count = file.write(contents);
		if (count < 0) {
			QMessageBox::warning(this, "Write error", QString("Failed to write: %1").arg(file.fileName()));
			return;
		}
		contents = contents.mid(count);
	}
	file.close();

	// 2. Add to downloads tab
	// TODO

	// 3. Open in viewer
	PDFTool pdfTool(fileName);
	QString title = pdfTool.guessTitle();
	if (title.trimmed().isEmpty())
		title = "Downloaded";
	openFile(fileName, title, QByteArray(), true);
}

void MainWindow::on_txtSearch_editingFinished()
{
	if (!ui->txtSearch->text().isEmpty()) {
		QList<LibrarySearchResult> results = library->search(ui->txtSearch->text());
		populateLibraryWidgetSearch(results);
	} else {
		populateLibraryWidget();
		restoreTreeViewSortOrder();
	}
}

void MainWindow::onRatingChanged(QByteArray hash, int rating)
{
	if (hash.isEmpty())
		return;
	library->setFileData(hash, "Rating", QString::number(rating));
}

void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
{
	libraryContextMenu->popup(ui->treeLibrary->mapToGlobal(pos));
}

void MainWindow::onLookupRequested()
{
	QByteArray hash = ui->treeLibrary->currentItem()->data(0, Qt::UserRole).toByteArray();
	if (hash.isEmpty())
		return;
	lookupDocHash = hash;
	QString searchText = library->getFileData(hash, "Title") + " " + library->getFileData(hash, "Author");
	browser->load(QString("http://scholar.google.ch/scholar?hl=en&q=%1").arg(searchText));
	ui->btnBrowser->setChecked(true);
}

void MainWindow::onDeleteRequested()
{
	QByteArray hash = ui->treeLibrary->currentItem()->data(0, Qt::UserRole).toByteArray();
	if (hash.isEmpty())
		return;
	if (QMessageBox::question(this, "Delete file from library", "Are you sure you want to remove this file from the library? This operation cannot be reversed.", QMessageBox::Yes, QMessageBox::No)
			!= QMessageBox::Yes)
		return;
	library->deleteFile(hash);
	populateLibraryWidget();
}

void MainWindow::onMetadataDownloaded(QString metadata)
{
	if (lookupDocHash.isEmpty())
		return;
	if (!library->getDocs().contains(lookupDocHash))
		return;
	QHash<QString, QString> parsedMetadata = metadataFromEndNote(metadata);
	if (parsedMetadata.isEmpty())
		return;
	QString text;
	foreach (QString key, parsedMetadata.keys()) {
		text += QString("%1: %2\n").arg(key).arg(parsedMetadata[key]);
	}

	if (QMessageBox::question(this, "Import metadata?",
						  QString("Document:\n"
								  "Current title: %1\n"
								  "Current authors: %2\n"
								  "\n"
								  "Metadata to import:\n"
								  "%3").arg(library->getFileData(lookupDocHash, "Title"))
						  .arg(library->getFileData(lookupDocHash, "Author"))
						  .arg(text),
						  QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
		return;

	foreach (QString key, parsedMetadata.keys()) {
		library->setFileData(lookupDocHash, key, parsedMetadata[key]);
	}
	updateLibraryItem(lookupDocHash);
	ui->btnLibrary->setChecked(true);
}

void MainWindow::updateLibraryItem(QByteArray docHash)
{
	LibraryDoc doc = library->getDocs().value(docHash);
	if (doc.isNull())
		return;

	for (QTreeWidgetItemIterator it(ui->treeLibrary); *it; ++it) {
		QTreeWidgetItem *item = *it;
		if (item->data(0, Qt::UserRole).toByteArray() == docHash) {
			populateLibraryWidgetItem(item, doc);
		}
	}
}


void MainWindow::on_btnAbout_clicked()
{
	QMessageBox::information(this,
							 "About Fundamental",
							 QString("<b>Fundamental</b> version %1<br />"
									 "<br />"
									 "<b>Website:</b> <a href='http://code.google.com/p/fundamental/'>http://code.google.com/p/fundamental/</a><br />"
									 "<br />"
									 "<b>License:</b> <a href='http://www.gnu.org/licenses/old-licenses/gpl-2.0.html'>GNU GPL v2</a><br />"
									 "<br />"
									 "<b>Thanks to:</b><br />"
									 "<a href='http://qt-project.org/'>http://qt-project.org/</a><br />"
									 "<a href='http://poppler.freedesktop.org/'>http://poppler.freedesktop.org/</a><br />"
									 "<a href='http://knowah.github.io/PyPDF2/'>http://knowah.github.io/PyPDF2/</a><br />"
									 "<a href='http://openclipart.org/detail/3redbooks.svg'>http://openclipart.org/detail/3redbooks.svg</a><br />"
									 "<a href='http://www.oxygen-icons.org/'>http://www.oxygen-icons.org/</a><br />"
									 "<a href='http://openclipart.org/detail/75799/registry-book-by-wakro'>http://openclipart.org/detail/75799/registry-book-by-wakro</a><br />")
							 .arg(VERSION),
							 QMessageBox::Ok);
}

void MainWindow::on_checkOptionsAutoGit_toggled(bool)
{
	if (ui->checkOptionsAutoGit->isChecked()) {
		if (!library->getAutoGit()) {
			QMessageBox::information(this,
									 "How to enable git",
									 QString("Git support will be enabled when the application is restarted.\n"
											 "\n"
											 "You need to follow these steps:\n"
											 "\n"
											 "1. Close Fundamental.\n"
											 "\n"
											 "2. Clean up and sync the library git repository (either commit and push your local "
											 "changes to keep them, or stash and pull to delete them). Be careful! You should backup "
											 "first by copying the entire Fundamental library directory to another location.\n"
											 "In case you want to commit, it is essential to commit the .fundamental file (metadata), "
											 "and all the *.pdf files (the actual documents).\n"
											 "\n"
											 "3. Start Fundamental. If everything is OK, git sync should be enabled."),
									 QMessageBox::Ok);
			saveAutoGitDelayed(true);
		}
	} else {
		saveAutoGitDelayed(false);
		QString message;
		library->setAutoGit(false, message);
		ui->checkOptionsAutoGit->setChecked(library->getAutoGit());
		saveAutoGit();
	}
}
